package org.dftproject.familysearch.core;

import static java.lang.String.format;
import static java.lang.System.err;
import static java.lang.System.out;
import static org.dftproject.familysearch.core.Constants.ROOT_SITE;
import static org.dftproject.familysearch.core.Constants.VERSION;
import static org.dftproject.familysearch.core.SearchDemo.streamDump;
import static org.dftproject.familysearch.core.SearchDemo.unmarshal;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.familysearch.api.familytree.v1.Assertions;
import org.familysearch.api.familytree.v1.Fact;
import org.familysearch.api.familytree.v1.FamilyTree;
import org.familysearch.api.familytree.v1.GenderType;
import org.familysearch.api.familytree.v1.Name;
import org.familysearch.api.familytree.v1.NameForm;
import org.familysearch.api.familytree.v1.Person;
import org.familysearch.api.familytree.v1.PersonInformation;
import org.familysearch.api.familytree.v1.PersonReference;
import org.familysearch.api.familytree.v1.FamilyTree.Persons;

public class PersonReadDemo {

	private static String SESSION_ID;

	private static void usage(String[] args) {
		if (args.length < 2) {
			int i = 0;
			out.printf("arg %d, user name%n", ++i);
			out.printf("arg %d, password%n", ++i);
			System.exit(1);
		}
	}

	/**
	 * @param args
	 */
	private static final HttpClient client = new HttpClient();

	public static final String READ_LOC = format("/familytree/%s/person", VERSION);
	public static final String READ_SITE = ROOT_SITE + READ_LOC;

	public static void main(String[] args) throws Throwable {
		usage(args);
		SESSION_ID = SearchDemo.login(client, args[0], args[1]);
		if (args.length >= 3) {
			readPerson(args[2]);
		} else
			readSelf();
	}

	private static void readSelf() throws IOException, JAXBException {
		readPerson("me");
	}

	private static void readPerson(String id) throws IOException, JAXBException {
		NameValuePair fullView = new NameValuePair("view", "full");
		NameValuePair noCitations = new NameValuePair("citations", "false");
		NameValuePair noNotes = new NameValuePair("notes", "false");
		NameValuePair ancestors = new NameValuePair("ancestors", "4");
		NameValuePair descendants = new NameValuePair("descendants", "1");
		NameValuePair sessionId = new NameValuePair("sessionId", SESSION_ID);
		String url = SearchDemo.buildUrl(format("%s/%s", READ_SITE, id), fullView, noCitations, noNotes, ancestors, descendants, sessionId);
		GetMethod method = new GetMethod(url);
		client.executeMethod(method);
		handleReadResult(method); // unmarshal and process the response data
	}

	private static void handleReadResult(GetMethod method) throws IOException, JAXBException {
		int responseCode = method.getStatusCode();
		if (responseCode >= 400) {
			SearchDemo.streamDump(method.getResponseBodyAsStream(), err);
			throw new RuntimeException(format("person read request failed with error code %d", responseCode));
		}
		InputStream in = method.getResponseBodyAsStream(); // response data
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		streamDump(in, bout); // dump the response data so we can print it out
		// to standard out if we want to
		streamDump(new ByteArrayInputStream(bout.toByteArray()), System.out); // optionally
		// print
		// the
		// response
		// to
		// standard
		// out
		out.println();
		FamilyTree tree = unmarshal(FamilyTree.class, new ByteArrayInputStream(bout.toByteArray()));
		Persons persons = tree.getPersons();
		assert persons.getPerson().size() >= 1;
		Person person = persons.getPerson().get(0);
		processPerson(person, mapPeople(persons.getPerson()));
	}

	private static Map<String, Person> mapPeople(Collection<Person> persons) {
		Map<String, Person> results = new HashMap<String, Person>();
		for (Person p : persons) {
			results.put(p.getId(), p);
		}
		return results;
	}

	private static void processPerson(Person person, Map<String, Person> peopleMap) {
		assert person != null;
		PersonInformation info = person.getInformation();
		Assertions assertions = person.getAssertions();
		String id = person.getId();
		GenderType gender = info.getGender();
		out.printf("Name: %s%n", getName(person));
		out.printf("Gender: %s%n", gender.name());
		processRelations(assertions.getFact(), peopleMap);
	}

	private static void processRelations(List<Fact> relations, Map<String, Person> peopleMap) {
		List<PersonReference> children = new LinkedList<PersonReference>();
		List<PersonReference> parents = new LinkedList<PersonReference>();
		List<PersonReference> spouses = new LinkedList<PersonReference>();
		for (Fact r : relations) {
			switch (r.getScope()) {
			case PARENT_CHILD:
				if (r.getChild() != null)
					children.add(r.getChild());
				else {
					assert r.getParent() != null;
					parents.add(r.getParent());
				}
				break;
			case COUPLE:
				assert r.getSpouse() != null;
				spouses.add(r.getSpouse());
				break;
			case PERSON:
				break;
			}
			out.printf("Fact of scope %s%n", r.getScope().name());
		}

		printReferences(spouses, peopleMap, "Spouses:");
		printReferences(children, peopleMap, "Children:");
		printReferences(parents, peopleMap, "Parents:");
	}

	private static void printReferences(List<PersonReference> references, Map<String, Person> peopleMap, String header) {
		List<Person> persons = new LinkedList<Person>();
		for (PersonReference pr : references) {
			Person p = peopleMap.get(pr.getRef());
			if (p == null)
				out.printf("%s failed to resolve reference %s%n", header, pr.getRef());
			else
				persons.add(p);
		}
		if (persons.size() > 0) {
			out.println(header);
			for (Person p : persons) {
				out.printf("\t%s%n", getName(p));
			}
		}
	}

	public static String getName(Person p) {
		List<NameForm> nameForms = null;
		if (p.getAssertions() != null) {
			Assertions a = p.getAssertions();
			List<Name> names = a.getName();
			if (names.size() == 0) {
				err.println("no names found!");
				return format("ID:%s", p.getId());
			} else {
				nameForms = names.get(0).getForms().getForm();
			}
		} else if (p.getSummary() != null) {
			nameForms = p.getSummary().getName().getForm();
		}
		if (nameForms.size() == 0) {
			err.println("no forms found!");
			return format("ID:%s", p.getId());
		} else
			return nameForms.get(0).getFullText();
	}
}
